﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using KuiHuaBaoDian.Services.Guide;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;

namespace KuiHuaBaoDian.GuideGraph.Editor {

    public class GuideGraphEntityNode : Node {

        protected delegate void PropertyChangedDelegate();
        protected event PropertyChangedDelegate PropertyChanged;

        public const string PORT_CLASS_INPUT = "guide_entity_port_input";
        public const string PORT_CLASS_OUTPUT = "guide_entity_port_output";

        public const string PORT_CLASS_PREV = "guide_step_start_port_prev";
        public const string PORT_CLASS_NEXT = "guide_step_start_port_next";

        public GuideEntity Entity => (GuideEntity)userData;

        public Port PortInput { get; private set; }
        public Port PortOutput { get; private set; }

        public Port PortPrev { get; private set; }
        public Port PortNext { get; private set; }

        public GuideGraphView View { get; private set; }

        public VisualElement titleLabelContainer { get; private set; }
        public VisualElement propertyContainer { get; private set; }
        public VisualElement nextContainer { get; private set; }

        private SerializedObject m_SO;
        private SerializedProperty m_PositionProperty;

        public GuideGraphEntityNode(GuideEntity entity, GuideGraphView view) {
            View = view;
            userData = entity;
            title = string.Empty;
            viewDataKey = Entity.GUID.ToString();
            style.left = Entity.Position.x;
            style.top = Entity.Position.y;
            titleButtonContainer.style.display = DisplayStyle.None;
            titleLabelContainer = new VisualElement() {
                name = "title-label-container",
            };
            titleContainer.Insert(1, titleLabelContainer);
            titleContainer.style.justifyContent = Justify.FlexStart;
            propertyContainer = new VisualElement() {
                name = "property-container",
            };
            mainContainer.Add(propertyContainer);
            nextContainer = new VisualElement {
                name = "next",
                pickingMode = PickingMode.Ignore
            };
            mainContainer.Add(nextContainer);
            InitializePorts();
            InitializeData();
            OnInitialized();
            Initialized();
            PropertyChanged?.Invoke();
            var titleAttribute = entity.GetType().GetCustomAttribute<NodeTitleAttribute>();
            entity.name = titleAttribute == null ? entity.name : titleAttribute.Value;
            if (titleLabelContainer.childCount == 0) {
                title = entity.name;
                titleLabelContainer.style.display = DisplayStyle.None;
            } else {
                var titleLabel = titleContainer.Q<Label>();
                titleLabel.style.display = DisplayStyle.None;
            }
        }

        protected virtual void OnInitialized() { }

        private void Initialized() {
            var labels = titleContainer.Query<Label>();
            labels.First().name = string.Empty;
            labels.Last().AddToClassList("title-label-last");
        }

        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) {
            base.BuildContextualMenu(evt);
        }

        private void InitializeData() {
            m_SO = new SerializedObject(Entity);
            m_PositionProperty = m_SO.FindProperty(nameof(Entity.m_Position));
            var memberInfos = Entity.GetType().GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            foreach (var memberInfo in memberInfos) {
                if (memberInfo is PropertyInfo) {
                    ShowMember(memberInfo);
                } else if (memberInfo is FieldInfo) {
                    ShowMember(memberInfo);
                }
            }
        }

        private void ShowMember(MemberInfo memberInfo) {
            var sp = m_SO.FindProperty(memberInfo.Name);
            if (sp != null) {
                var hideAttribute = memberInfo.GetCustomAttribute<HideInGuideAttribute>();
                if (hideAttribute != null) {
                    return;
                }
                var labelAttribute = memberInfo.GetCustomAttribute<LabelAttribute>();
                var label = labelAttribute == null ? memberInfo.Name : labelAttribute.Value;
                var defaultValueAttribute = memberInfo.GetCustomAttribute<DefaultValueAttribute>();
                var asEnumAttribute = memberInfo.GetCustomAttribute<AsEnumAttribute>();
                var type = memberInfo.MemberType == MemberTypes.Field ? (memberInfo as FieldInfo).FieldType : (memberInfo as PropertyInfo).PropertyType;
                if (View.PropertyEditorTypeDictionary.TryGetValue(type, out var editorType)) {
                    AddCustomPropertyField(label, editorType, sp);
                } else if (type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) || type == typeof(short) || type == typeof(ushort)) {
                    AddIntField(label, sp);
                } else if (type == typeof(float)) {
                    AddFloatField(label, sp);
                } else if (type == typeof(bool)) {
                    AddBoolField(label, sp);
                } else if (type == typeof(Vector2)) {
                    AddVector2Field(label, sp);
                } else if (type == typeof(Vector2Int)) {
                    AddVector2IntField(label, sp);
                } else if (type == typeof(string) && asEnumAttribute == null) {
                    var multiplyAttribute = memberInfo.GetCustomAttribute<MultilineAttribute>();
                    var maxWidthAttribute = memberInfo.GetCustomAttribute<MaxWidthAttribute>();
                    var widthAttribute = memberInfo.GetCustomAttribute<WidthAttribute>();
                    AddStringField(label, sp, isMultiply: multiplyAttribute != null, width: widthAttribute != null ? widthAttribute.Value : 0, maxWidth: maxWidthAttribute != null ? maxWidthAttribute.Value : 0);
                } else if (defaultValueAttribute != null && typeof(Enum).IsAssignableFrom(defaultValueAttribute.Value.GetType())) {
                    AddEnumField(label, defaultValueAttribute.Value.GetType(), (Enum)defaultValueAttribute.Value, sp, asEnumAttribute != null);
                } else {
                    ShowMember(label, type, sp);
                }
            }
        }

        protected virtual void ShowMember(string label, Type type, SerializedProperty sp) { }

        public override void SetPosition(Rect newPos) {
            base.SetPosition(newPos);
            var position = new Vector2(newPos.xMin, newPos.yMin);
            m_PositionProperty.vector2Value = position;
            m_SO.ApplyModifiedPropertiesWithoutUndo();
        }

        public void AddEdge(Edge edge) {
            if (edge.output == PortOutput) {
                Entity.OutputTo = (edge.input.node.userData as GuideEntity).GUID;
            } else if (edge.output == PortNext) {
                Entity.NextTo = (edge.input.node.userData as GuideEntity).GUID;
            }
        }
        public void RemoveEdge(Edge edge) {
            if (edge.output == PortOutput) {
                Entity.OutputTo = Guid.Empty;
            } else if (edge.output == PortNext) {
                Entity.NextTo = Guid.Empty;
            }
        }

        private void InitializePorts() {
            var entityType = Entity.GetType();
            if (Entity is IGuideEntityHasInput && entityType.GetCustomAttribute(typeof(NodeHasNotInputAtrribute)) == null) {
                PortInput = InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(bool));
                PortInput.portName = string.Empty;
                PortInput.AddToClassList(PORT_CLASS_INPUT);
                inputContainer.Add(PortInput);
            }
            if (Entity is IGuideEntityHasOutput && entityType.GetCustomAttribute(typeof(NodeHasNotOutputAtrribute)) == null) {
                PortOutput = InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(bool));
                PortOutput.portName = string.Empty;
                PortOutput.AddToClassList(PORT_CLASS_OUTPUT);
                outputContainer.Add(PortOutput);
            }
            if (Entity is IGuideEntityHasPrev) {
                PortPrev = InstantiatePort(Orientation.Vertical, Direction.Input, Port.Capacity.Single, typeof(bool));
                PortPrev.portName = string.Empty;
                PortPrev.AddToClassList(PORT_CLASS_PREV);
                PortPrev.style.height = 36;
                titleContainer.Insert(0, PortPrev);
            }
            if (Entity is IGuideEntityHasNext) {
                PortNext = InstantiatePort(Orientation.Vertical, Direction.Output, Port.Capacity.Single, typeof(bool));
                PortNext.portName = string.Empty;
                PortNext.AddToClassList(PORT_CLASS_NEXT);
                nextContainer.Add(PortNext);
            }
        }

        private void AddCustomPropertyField(string label, Type editorType, SerializedProperty sp) {
            var editor = (IGuideProperty)Activator.CreateInstance(editorType);
            var field = editor.GetField(label, sp);
            field.RegisterCallback<IGuideProperty.PropertyChangedEvent>(evt => {
                PropertyChanged?.Invoke();
            });
            propertyContainer.Add(field);
        }

        private void AddIntField(string label, SerializedProperty sp) {
            var field = new IntegerField {
                value = sp.intValue
            };
            AddField(field, label, sp, newValue => sp.intValue = newValue);
        }

        private void AddVector2Field(string label, SerializedProperty sp) {
            var field = new Vector2Field {
                value = sp.vector2Value
            };
            AddField(field, label, sp, newValue => sp.vector2Value = newValue);
        }

        private void AddVector2IntField(string label, SerializedProperty sp) {
            var field = new Vector2IntField {
                value = sp.vector2IntValue
            };
            AddField(field, label, sp, newValue => sp.vector2IntValue = newValue);
        }

        private void AddFloatField(string label, SerializedProperty sp) {
            var field = new FloatField {
                value = sp.floatValue
            };
            AddField(field, label, sp, newValue => sp.floatValue = newValue);
        }

        private void AddBoolField(string label, SerializedProperty sp) {
            var field = new Toggle {
                value = sp.boolValue
            };
            AddField(field, label, sp, newValue => sp.boolValue = newValue);
        }

        private void AddStringField(string label, SerializedProperty sp, bool isMultiply = false, int width = 0, int maxWidth = 0) {
            var field = new TextField {
                value = sp.stringValue,
                multiline = isMultiply,
            };
            if (width > 0) {
                field.style.width = width;
            }
            if (maxWidth > 0) {
                field.style.maxWidth = maxWidth;
            }
            if (isMultiply) {
                field.style.whiteSpace = WhiteSpace.Normal;
            }
            AddField(field, label, sp, newValue => sp.stringValue = newValue);
        }

        private void AddField<T>(BaseField<T> field, string label, SerializedProperty sp, Action<T> changed) {
            if (field is INotifyValueChanged<T> control)
                field.RegisterValueChangedCallback(evt => {
                    changed?.Invoke(evt.newValue);
                    sp.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                    PropertyChanged?.Invoke();
                });
            var fieldLabel = new Label(label) {
                name = "title-label"
            };
            fieldLabel.AddToClassList("title-label-last");
            field.Insert(0, fieldLabel);
            propertyContainer.Add(field);
        }

        private void AddEnumField(string label, Type enumType, Enum defaultValue, SerializedProperty sp, bool useString) {
            string stringValue = null;
            if (useString) {
                stringValue = sp.stringValue;
                if (string.IsNullOrWhiteSpace(stringValue)) {
                    stringValue = defaultValue.ToString();
                    sp.stringValue = stringValue;
                    sp.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                }
            } else {
                stringValue = sp.enumNames[sp.enumValueIndex];
            }
            var enumValue = defaultValue;
            if (Enum.TryParse(enumType, stringValue, true, out var e)) {
                enumValue = (Enum)e;
            }
            string enumDisplayName = null;
            var enumNames = Enum.GetNames(enumType).OrderBy(item => item).ToArray();
            var enumDisplayNameDictionary = new Dictionary<string, int>();
            var removeList = new List<string>();
            for (var i = 0; i < enumNames.Length; i++) {
                var enumName = enumNames[i];
                var fieldInfo = enumType.GetField(enumName);
                var hideAttribute = fieldInfo.GetCustomAttribute<HideInGuideAttribute>();
                var obsoleteAttribute = fieldInfo.GetCustomAttribute<ObsoleteAttribute>();
                if (hideAttribute == null && obsoleteAttribute == null) {
                    var labelAttribute = fieldInfo.GetCustomAttribute<LabelAttribute>();
                    string addedName = null;
                    if (labelAttribute != null) {
                        var displayName = labelAttribute.Value;
                        addedName = displayName;
                    } else {
                        addedName = enumName;
                    }
                    enumDisplayNameDictionary[addedName] = i;
                    if (enumValue.ToString() == enumName) {
                        enumDisplayName = addedName;
                    }
                }
            }
            var enumDisplayNameList = enumDisplayNameDictionary.OrderBy(kv => kv.Value).Select(kv => kv.Key).ToList();
            var selectedIndex = enumDisplayNameList.IndexOf(enumDisplayName);
            var enumField = new DropdownField(enumDisplayNameList, selectedIndex);
            var fieldLabel = new Label(label) {
                name = "title-label"
            };
            enumField.Insert(0, fieldLabel);
            enumField.style.unityFontStyleAndWeight = FontStyle.Bold;
            enumField.RegisterValueChangedCallback(evt => {
                var index = enumDisplayNameDictionary[evt.newValue];
                var originValueName = enumNames[index];
                if (Enum.TryParse(enumType, originValueName, out var newValue)) {
                    if (useString) {
                        sp.stringValue = originValueName;
                    } else {
                        sp.enumValueIndex = sp.enumNames.IndexOf(originValueName);
                    }
                    sp.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                    PropertyChanged?.Invoke();
                }
            });
            fieldLabel.AddToClassList("title-label-last");
            propertyContainer.Add(enumField);
        }

        protected string GetEnumLabel(Enum value) {
            var enumType = value.GetType();
            var enumName = value.ToString();
            var field = enumType.GetField(enumName);
            var labelAttribute = field.GetCustomAttribute<LabelAttribute>();
            return labelAttribute == null ? enumName : labelAttribute.Value;
        }
    }
}
